/*
 * Middleware
 * Copyright (C) 2010/11
 * Riccardo Pulvirenti, Giuseppe Ravidà, Andrea Tino
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANDABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses
 */

/*!
 * File: core.cpp
 */

#include <boost/thread.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <string>
#include <exception>

#include "core.hpp"
#include "ui.hpp"
#include "manager.hpp"
#include "worker_discovery.hpp"
#include "worker.hpp"
#include "worker_descriptor.hpp"
#include "address.hpp"
#include "utils.hpp"
#include "data.hpp"

using namespace middleware;

//-----------------------------------------------------------------------------
// Constructors and destructor
/* Default constructor */
CORE::CORE() {
	this->_config_filename = kConfigFileName;
	this->_logger_postfix = utils::GetRandomIntegralNumberStr(1, 10000);
}
/* Constructor specifying file name */
CORE::CORE(const std::string& filename, const std::string& log_postfix) {
	this->_config_filename = filename;
	this->_logger_postfix = log_postfix;
}
/* Destructor */
CORE::~CORE() {

}
//-----------------------------------------------------------------------------
// Public members
/* Execution for core (NOT A THREAD) */
void CORE::exec() {
	std::cout << kCOREEtq << "CORE now started!" << std::endl;
	try {
		// Bonjour
		this->bonjour();
		// Managing knowledge -------------------------------------------------
		std::cout << kCOREEtq << "Initializing Peers Knowledge..." << std::endl;
		this->_peers_worker_descriptors.clear();
		WorkerCapabilities supp_wc; /* Automatically default (null) generated */
		supp_wc = kDefWorkerCapab;
		unsigned int peers_num =
				boost::lexical_cast<unsigned int>(((*this)[kCPOtherPeersNum]));
		for (unsigned int i = 1; i <= peers_num; i++) {
			std::string supp_str_1 = kCPAddrPeerXIpPre +
					boost::lexical_cast<std::string>(i) + kCPAddrPeerXIpPost;
			std::string supp_str_2 = kCPAddrPeerXTaskDescsPortPre +
					boost::lexical_cast<std::string>(i) + kCPAddrPeerXTaskDescsPortPost;
			std::string supp_str_3 = kCPAddrPeerXWorkerDescsPortPre +
					boost::lexical_cast<std::string>(i) + kCPAddrPeerXWorkerDescsPortPost;
			std::string supp_str_4 = kCPAddrPeerXDataPortPre +
					boost::lexical_cast<std::string>(i) + kCPAddrPeerXDataPortPost;
			Address supp_addr(
					boost::lexical_cast<InetIpAddr>(((*this)[supp_str_1])),
					boost::lexical_cast<InetPort>(((*this)[supp_str_2])),
					boost::lexical_cast<InetPort>(((*this)[supp_str_3])),
					boost::lexical_cast<InetPort>(((*this)[supp_str_4])));
			WorkerDescriptor supp_wd(supp_addr, supp_wc);
			std::cout << kCOREEtq << "Inserted known peer in PK: IP=" <<
					supp_addr.get_ip() << " manPORT=" << supp_addr.get_port() <<
					" discPORT=" << supp_addr.get_port_disc() <<
					" wPORT=" << supp_addr.get_port_w() << std::endl;
			this->_peers_worker_descriptors.push_back(supp_wd); /* First */
		}
		// Knowledge reference
		PeersKnowledge PK;
		PK.ptr_to_worker_descriptors = &(this->_peers_worker_descriptors);
		PK.ptr_to_worker_descriptors_mutex =
				&(this->_peers_worker_descriptors_mutex);
		// Launch threads
		/* DISC ------------------------------------------------------------ */
		DiscoveryConfig discconf;
		std::cout << kCOREEtq << "Configuring Discovery..." << std::endl;
		discconf.disc_ip_tow_bind = ((*this)[kCPAddrBindIp]);
		discconf.disc_port_tow_bind = ((*this)[kCPAddrDiscBindPort]);
		discconf.log_postfix = this->_logger_postfix;
		WorkerDiscovery disc(PK, discconf);
		/* W --------------------------------------------------------------- */
		Address addr; /* For WorkerDescriptor */
		WorkerCapabilities capab;  /* For WorkerDescriptor */
		std::cout << kCOREEtq << "Configuring Worker..." << std::endl;
		addr.set_ip(boost::lexical_cast<InetIpAddr>(
				((*this)[kCPAddrBindIp]))); /* IMPORTANT */
		addr.set_port(boost::lexical_cast<InetPort>(
				((*this)[kCPAddrManBindPort]))); /* IMPORTANT */
		addr.set_port_disc(boost::lexical_cast<InetPort>(
				((*this)[kCPAddrDiscBindPort]))); /* IMPORTANT */
		addr.set_port_w(boost::lexical_cast<InetPort>(
				((*this)[kCPAddrWBindPort]))); /* VERY IMPORTANT */
		capab.capab_bandwidth = boost::lexical_cast<double>(
				((*this)[kCPWorkerCapabB]));
		capab.capab_core_num = boost::lexical_cast<unsigned int>(
				((*this)[kCPWorkerCapabC]));
		capab.capab_cpu_mhz = boost::lexical_cast<double>(
				((*this)[kCPWorkerCapabZ]));
		capab.capab_cpu_num = boost::lexical_cast<unsigned int>(
				((*this)[kCPWorkerCapabP]));
		capab.capab_hops = boost::lexical_cast<unsigned int>(
				((*this)[kCPWorkerCapabD]));
		capab.capab_ram_mb = boost::lexical_cast<double>(
				((*this)[kCPWorkerCapabM]));
		WorkerDescriptor wd(addr, capab);
		WConfig wconf; wconf.log_postfix = this->_logger_postfix;
		Worker worker(wconf, PK, wd);
		/* MAN ------------------------------------------------------------- */
		std::cout << kCOREEtq << "Configuring Manager..." << std::endl;
		ManagerConfig manconf;
		manconf.man_ip_toman_bind = ((*this)[kCPAddrBindIp]);
		manconf.man_port_toman_bind = ((*this)[kCPAddrManBindPort]);
		manconf.ptrto_worker = &worker;
		manconf.ptrto_discovery = &disc;
		manconf.log_postfix = this->_logger_postfix;
		Manager man(manconf);
		/* UI -------------------------------------------------------------- */
		std::cout << kCOREEtq << "Configuring UI..." << std::endl;
		UIConfig uiconf;
		uiconf.ptrto_manager = &man;
		uiconf.log_postfix = this->_logger_postfix;
		UI ui(uiconf);
		// Running ------------------------------------------------------------
		std::cout << kCOREEtq << "Running main components..." << std::endl;
		man.run();
		ui.run();
		worker.run();
		disc.run();
		std::cout << kCOREEtq << "Joining on main components..." << std::endl;
		// Joining ------------------------------------------------------------
		man.join();
		ui.join();
		worker.join();
		disc.join();
	} catch(CoreException& e) {
		std::cout << kCOREEtq << "Top Level Error: " << e.what() << std::endl;
		std::cout << kCOREEtq << "Now exiting!" << std::endl;
	} catch(std::exception e) {
		std::cout << kCOREEtq << "Top Level Error: " << e.what() << std::endl;
		std::cout << kCOREEtq << "Now exiting!" << std::endl;
	}
	std::cout << kCOREEtq << "Exiting..." << std::endl;
}
/* Param indexer */
std::string CORE::operator[](const std::string& param_name)
		throw(CoreException) {
	std::string line;
	std::string par;
	std::ifstream config_file;
	int delimiter = 0;
	this->Open(config_file);
	if (config_file.is_open()) {
		while (config_file.good()) {
			getline(config_file, line); /* Getting varX:varY */
			if (line.substr(0, (delimiter =
					line.find(kConfigFileDelimiter))) == param_name) {
				// Found ==> Getting value
				this->Close(config_file);
				return line.substr((delimiter + 1), line.size() - delimiter);
			}
		}
		this->Close(config_file);
		// Not found
		return kParameterNotFound;
	} else {
		throw CoreException("Impossible to access data! Invalid configuration file.");
	}
}
//-----------------------------------------------------------------------------
// Private members
void CORE::bonjour() throw(CoreException) {
	std::cout << kCOREEtq << "Initialization sequence now started..." <<
				std::endl;
	std::cout << kCOREEtq << "Checking configuration file: " <<
			this->_config_filename << " (local path)..." << std::endl;
	try {
		std::ifstream config_file;
		this->Open(config_file);
		this->Close(config_file);
	} catch(CoreException& e) {
		std::cout << "Could not open configuration file!" <<
				std::endl;
		throw CoreException("Incorrect configuration file.");
	}
	std::cout << kCOREEtq << "Configuration file ok!" << std::endl;
	std::cout << kCOREEtq << "Initialization completed!" << std::endl;
}
/* File opener */
void CORE::Open(std::ifstream& config_file) throw(CoreException) {
	config_file.open(this->_config_filename.c_str());
	if (!(config_file.is_open()))
		throw CoreException("Unable to open configuration file!");
}
/* File closing */
void CORE::Close(std::ifstream& config_file) {
	config_file.close();
}
